/*
 * Copyright 2007 Dan Hodge
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.datajanitor.baja;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

import net.datajanitor.baja.data.FileInfo;
import net.datajanitor.baja.data.UserInfo;
import net.datajanitor.baja.spi.Authenticator;
import net.datajanitor.baja.spi.BoxConnectionProvider;
import net.datajanitor.baja.spi.BoxSpiException;
import net.datajanitor.baja.spi.data.AuthInfo;
import net.datajanitor.baja.spi.data.FileInfoTree;
import net.datajanitor.baja.spi.data.FindFileVisitor;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class BoxConnectionImpl implements BoxConnection {
    private static final Log logger = LogFactory.getLog(BoxConnectionImpl.class);
    
    private final BoxConnectionProvider provider;
    private final AuthInfo authInfo;
    private FileInfoTree tree;

    private void streamFile(FileInfo file, OutputStream outs) throws BoxException {
        InputStream ins = null;
        
        try {
            ins = new BufferedInputStream(this.provider.streamFile(this.authInfo, file));
            
            byte[] buf = new byte[8192];
            int bytesRead = 0;
            while (bytesRead > -1) {
                outs.write(buf, 0, bytesRead);
                bytesRead = ins.read(buf);
            }
        } catch (BoxSpiException e) {
            throw new BoxException("Error download file: " + file, e);    
        } catch (IOException e) {
            throw new BoxException("Error writing file: " + file + " to stream", e);    
        } finally {
            if (ins != null) {
                try {
                    ins.close();
                } catch (IOException e) {
                }
            }
        }        
    }
      
    public BoxConnectionImpl(Authenticator authenticator, BoxConnectionProvider provider) throws BoxConnectionException {
        try {
            this.provider = provider;
            this.authInfo = authenticator.authenticate(this.provider);
            
            this.tree = this.provider.getTree(this.authInfo);
            if (logger.isDebugEnabled()) {
                logger.debug("Successfully authenticated user: " + this.authInfo.getUser());
            }            
        } catch (BoxSpiException e) {
            throw new BoxConnectionException("Internal error occured while authenticating", e);    
        }
    }

    public void close() {
        this.authInfo.invalidate();
    }
    
    public UserInfo getUser() {
        return this.authInfo.getUser();
    }

    public void downloadFile(FileInfo file, File toFile) throws BoxException {
        OutputStream outs = null;
        
        try {
            outs = new BufferedOutputStream(new FileOutputStream(toFile));
            streamFile(file, outs);
        } catch (IOException e) {
            throw new BoxException("Error opening file: " + toFile, e);
        } finally {
            if (outs != null) {
                try {
                    outs.close();
                } catch (IOException e) {
                }
            }
        }        
    }

    public byte[] downloadFile(FileInfo file) throws BoxException {
        ByteArrayOutputStream outs = null;
        
        try {
            outs = new ByteArrayOutputStream();
            streamFile(file, outs);
            
            return outs.toByteArray();
        } finally {
            if (outs != null) {
                try {
                    outs.close();
                } catch (IOException e) {
                }
            }
        }        
    }

    public InputStream streamFile(FileInfo file) throws BoxException {
        try {
            return this.provider.streamFile(authInfo, file);
        } catch (BoxSpiException e) {
            throw new BoxException("Error streaming file: " + file, e);     
        }
    }
    
    public List<FileInfo> findFileByName(FileInfo baseDir, String name) throws BoxException {
        if (this.authInfo.isAuthenticated()) {
            FindFileVisitor visitor = new FindFileVisitor(name);
            this.tree.acceptAt(baseDir, visitor);
        
            return visitor.getFiles();
        } else {
            throw new BoxConnectionException("Connection is closed");
        }
    }

    public FileInfo getRootDirectory() throws BoxConnectionException {
        if (this.authInfo.isAuthenticated()) {
            return this.tree.getRoot();
        } else {
            throw new BoxConnectionException("Connection is closed");
        }
    }
    
    public FileInfo uploadFile(FileInfo toDir, File file) throws BoxException {
        try {
            return this.provider.uploadFile(this.authInfo, toDir, file);
        } catch (BoxSpiException e) {
            throw new BoxException("Error uploading file: " + file + " to dir: " + toDir, e);    
        }
    }

    public FileInfo uploadFile(FileInfo toDir, byte[] data, String name) {
        try {
            return this.provider.uploadFile(this.authInfo, toDir, data, name);
        } catch (BoxSpiException e) {
            throw new BoxException("Error uploading data: " + name + " to dir: " + toDir, e);    
        }        
    }

    public FileInfo uploadStream(FileInfo toDir, InputStream ins, String name) {
        File tmpFile = null;
        OutputStream outs = null;
        
        try {
            // write the stream to a tmp file so it can be uploaded
            tmpFile = new File(System.getProperty("java.io.tmpdir"), name);
            outs = new FileOutputStream(tmpFile);
           
            byte[] buf = new byte[8192];
            int bytesRead = 0;
            while (bytesRead > -1) {
                outs.write(buf, 0, bytesRead);
                bytesRead = ins.read(buf);
            }
            
            return uploadFile(toDir, tmpFile);
        } catch (IOException e) {
            throw new BoxException("Error writing stream to file: " + tmpFile, e);
        } finally {
            if (outs != null) {
                try {
                    outs.close();
                } catch (IOException ignore) {
                }
            }
            
            if (tmpFile != null && tmpFile.exists()) {
                if (!tmpFile.delete()) {
                    logger.error("Error deleting temporary file: " + tmpFile.getAbsolutePath());    
                }
            }
        }        
    }
}